/**
 * Copyright (c) 2008, Andrew Carter
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are 
 * permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer. Redistributions in binary form must reproduce 
 * the above copyright notice, this list of conditions and the following disclaimer in 
 * the documentation and/or other materials provided with the distribution. Neither the 
 * name of Andrew Carter nor the names of contributors may be used to endorse or promote 
 * products derived from this software without specific prior written permission. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.acarter.ardor3d.jtree;

import java.awt.Point;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetContext;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;

import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import com.acarter.composabletree.ComposableTreeModel;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;


/**
 * @author Carter
 */
public abstract class JTreeDragAndDrop implements DragSourceListener, DropTargetListener, DragGestureListener {

	/**  */
	protected DragSource source;

	/**  */
	protected DropTarget target;

	/**  */
	protected DragGestureRecognizer recognizer;

	/**  */
	protected JTreeTransferable transferable;

	/**  */
	protected Spatial spatial;

	/**  */
	protected Node parentNode;

	/**  */
	protected Ardor3dJTree tree;

	/**
	 * @param tree
	 * @param bridge
	 * @param actions
	 */
	public JTreeDragAndDrop(Ardor3dJTree tree, int actions) {

		this.tree = tree;

		source = new DragSource();
		target = new DropTarget(tree, this);

		recognizer = source.createDefaultDragGestureRecognizer(tree, actions, this);
	}

	/**
	 * Drag Gesture Handler
	 */
	public void dragGestureRecognized(DragGestureEvent dge) {

		TreePath path = tree.getSelectionPath();

		if((path == null)) {// || (path.getPathCount() <= 1)) {
			// We can't move the root node or an empty selection
			return;
		}

		Object object = path.getLastPathComponent();

		if(object instanceof Spatial) {

			spatial = (Spatial) object;

			transferable = new JTreeTransferable(path);

			source.startDrag(dge, null, transferable, this);
		}
	}

	/**
	 * Drag Event Handlers
	 */
	public void dragEnter(DragSourceDragEvent dsde) {

	}

	/**
	 * 
	 */
	public void dragExit(DragSourceEvent dse) {

	}

	/**
	 * 
	 */
	public void dragOver(DragSourceDragEvent dsde) {

	}

	/**
	 * 
	 */
	public void dropActionChanged(DragSourceDragEvent dsde) {

	}

	/**
	 * 
	 */
	public void dragDropEnd(DragSourceDropEvent dsde) {

		if(dsde.getDropSuccess() && (parentNode != null) && (spatial != null)) {

			ComposableTreeModel model = (ComposableTreeModel) tree.getModel();

			TreePath path = model.getPathToRoot(spatial);

			int[] i = { model.getIndexOfChild(path.getParentPath().getLastPathComponent(), spatial) };
			Object[] c = { spatial };
			TreeModelEvent evt = new TreeModelEvent(this, path.getParentPath(), i, c);

			if(doDragDropEnd(parentNode, spatial)) {

				model.fireTreeNodesRemoved(evt);
				
				path = model.getPathToRoot(spatial);
				
				int[] i2 = { model.getIndexOfChild(parentNode, spatial) };
				Object[] c2 = { spatial };
				evt = new TreeModelEvent(this, path.getParentPath(), i2, c2);

				model.fireTreeNodesInserted(evt);
			}
		}

		parentNode = null;
		spatial = null;
	}
	
	protected abstract boolean doDragDropEnd(Node parentNode, Spatial spatial);

	/**
	 * Drop Event Handlers
	 */
	private TreeNode getNodeForEvent(DropTargetDragEvent dtde) {

		Point p = dtde.getLocation();
		DropTargetContext dtc = dtde.getDropTargetContext();

		Ardor3dJTree tree = (Ardor3dJTree) dtc.getComponent();
		TreePath path = tree.getClosestPathForLocation(p.x, p.y);

		Object object = path.getLastPathComponent();

		if(object instanceof Spatial)
			return new JTreeNode((Spatial) object);

		return null;
	}

	/**
	 * 
	 */
	public void dragEnter(DropTargetDragEvent dtde) {

		TreeNode node = getNodeForEvent(dtde);

		if((node == null) || (node.isLeaf()))
			dtde.rejectDrag();
		else
			dtde.acceptDrag(DnDConstants.ACTION_MOVE);
	}

	/**
	 * 
	 */
	public void dragOver(DropTargetDragEvent dtde) {

		TreeNode node = getNodeForEvent(dtde);

		if((node == null) || (node.isLeaf()))
			dtde.rejectDrag();
		else
			dtde.acceptDrag(DnDConstants.ACTION_MOVE);
	}

	/**
	 * 
	 */
	public void dragExit(DropTargetEvent dte) {

	}

	/**
	 * 
	 */
	public void dropActionChanged(DropTargetDragEvent dtde) {

	}

	/**
	 * 
	 */
	public void drop(DropTargetDropEvent dtde) {

		Point pt = dtde.getLocation();
		DropTargetContext dtc = dtde.getDropTargetContext();

		Ardor3dJTree tree = (Ardor3dJTree) dtc.getComponent();
		TreePath parentpath = tree.getClosestPathForLocation(pt.x, pt.y);

		Object parentObject = parentpath.getLastPathComponent();

		if(parentObject instanceof Node) {

			JTreeNode parent = new JTreeNode((Node) parentObject);

			if(parent.isLeaf()) {

				dtde.rejectDrop();
				return;
			}

			parentNode = (Node) parentObject;

			dtde.dropComplete(true);
			return;
		}
	}
}
